[Previous] [Next]

The Field Object

The Recordset object exposes the Fields collection, which in turn contains one or more Field objects. Each Field object represents one column in the data source and exposes 12 properties and 2 methods.

Properties

The properties of a Field object can be divided into two distinct groups: properties that describe the attributes and the characteristics of the field (and that are available also when the Recordset is closed) and properties that describe the contents of a field in the current record. (These properties are available only when the Recordset is open and the current record isn't invalid.)

Describing the field's characteristics

All the properties that describe the Field object's characteristics (which are also known as metadata properties) are read/write if you're adding the Field object to a stand-alone Recordset and read-only after the Recordset has been opened.

The Name property is the name of the database column the Field object takes data from and writes data to. Because this property is also the key associated with the Field object in the Fields collection, you can refer to a particular field in one of three ways, using the following syntax:

' Full syntax
rs.Fields("LastName").Value = "Smith"
' Fields is the Recordset's default property.
rs("LastName").Value = "Smith"  
' Value is the Field's default property.
rs("LastName") = "Smith"

You usually enumerate the fields in a Recordset using a For…Next or For Each…Next loop:

For i = 0 To rs.Fields.Count _ 1
    lstFieldNames.AddItem rs.Fields(i).Name
Next

The Type property returns an enumerated constant that defines which kind of values can be stored in the field. All the types that ADO supports are listed in Table 13-5, but you should be aware that not all OLE DB providers and database engines support all these data types. The Type property also indirectly affects NumericScale, Precision, and DefinedSize.

CAUTION
A few constants in Table 13-5 apply only to Parameter objects (which are described later in this chapter)—at least, this is what the Visual Basic documentation states. I found, however, that some of these values are also used for Field objects. For example, the Type property of a string field in an MDB database returns the value adVarChar.

Table 13-5. The constants used for the Type property of the Field, Parameter, and Property objects.

Constant Value Description
adEmpty 0 No value specified
adSmallInt 2 2-byte signed integer
adInteger 3 4-byte signed integer
adSingle 4 Single-precision floating point value
adDouble 5 Double-precision floating point value
adCurrency 6 Currency value
adDate 7 Date value (stored in a Double value, in the same format as Visual Basic's Date variables)
adBSTR 8 Null-terminated Unicode string
adIDispatch 9 Pointer to an IDispatch interface of an OLE object
adError 10 32-bit error code
adBoolean 11 Boolean value
adVariant 12 Variant value
adIUnknown 13 Pointer to an IUnknown interface of an OLE object
adDecimal 14 Numeric value with fixed precision and scale
adTinyInt 16 1-byte signed integer
adUnsignedTinyInt 17 1-byte unsigned integer
adUnsignedSmallInt 18 2-byte unsigned integer
adUnsignedInt 19 4-byte unsigned integer
adBigInt 20 8-byte signed integer
adUnsignedBigInt 21 8-byte unsigned integer
adGUID 72 Globally Unique Identifier (GUID)
adBinary 128 Binary value
adChar 129 String value
adWChar 130 Null-terminated Unicode character string
adNumeric 131 Exact numeric value with fixed precision and scale
adUserDefined 132 User-defined variable
adDBDate 133 Date value in format "yyyymmdd"
adDBTime 134 Time value in format "hhmmss"
adDBTimeStamp 135 Date and time stamp in format "yyyymmddhhmmss" plus a fraction in billionths
adChapter 136 Chapter (a dependent Recordset in a hierarchical Recordset)
adVarNumeric 139 Variable-length exact numeric value with fixed precision and scale
adVarChar 200 String value (Parameter object only)
adLongVarChar 201 Long variable-length character string (Parameter object only)
adVarWChar 202 Null-terminated Unicode character string (Parameter object only)
adLongVarWChar 203 Long variable-length Unicode character string (Parameter object only)
adVarBinary 204 Binary value (Parameter object only)
adLongVarBinary 205 Long variable-length binary data (Parameter object only)

The DefinedSize property returns the maximum capacity that was defined when the field was created. The NumericScale property indicates the scale of numeric values (in other words, the number of digits to the right of the decimal point that will be used to represent the value). The Precision property is the degree of precision for numeric values in a numeric Field object (that is, the maximum total number of digits used to represent the value). The Attributes property is a bit-field value that returns information about the field. It can contain one or more of the constants listed in Table 13-6.

Table 13-6. Constants used for the Attributes property of the Field object.

Constant Value Description
adFldMayDefer 2 A deferred field—that is, a field whose value is retrieved only when the field is explicitly referenced in the code. BLOB and CLOB fields are often fields of this type.
adFldUpdatable 4 The field is updatable.
adFldUnknownUpdatable 8 The provider can't determine whether the field is writable.
adFldFixed &H10 The field contains fixed-length data.
adFldIsNullable &H20 The field accepts Null values.
adFldMayBeNull &H40 The field can contain Null values (but doesn't necessarily accept them).
adFldLong &H80 The field is a long binary field (for example, a BLOB or a CLOB), and you can use the AppendChunk and GetChunk methods on it.
adFldRowID &H100 The field contains a record identifier that can't be written to and that has no meaningful value except for identifying the row (for example, a record number or a unique identifier).
adFldRowVersion &H200 The field contains some kind of date stamp or time stamp that is used to track record updates.
adFldCacheDeferred &H1000 The field is cached the first time it is read from the database, and all subsequent reads fetch the data from the cache.
adFldKeyColumn &H8000 The field is part of the key.

Describing the field's value

The Value property sets or returns the contents of the field. It is also the default property of the Field object, so you can omit it if you want to:

rs.Fields("BirthDate") = #4/12/1955#

The ActualSize property is a read-only property that returns the number of bytes taken by the current value in the field. Don't confuse this property with the DefinedSize property, which returns the declared maximum length of the field. This property is especially useful with BLOB and CLOB fields. If the provider can't determine the size of the field, it returns that value -1. (The Visual Basic documentation states that in this case this property returns the constant adUnknown; however, this constant doesn't appear in the ADODB type library.)

The OriginalValue property returns the value that was in the field before any changes were made. If you're in immediate update mode, this is the value that the CancelUpdate method uses to restore the field's contents; if you're in batch update mode, this is the value that was valid after the last UpdateBatch method and is also the value that the CancelBatch method uses to restore the field's contents.

The UnderlyingValue property is the value that is currently stored in the database. This value might be different from the OriginalValue property if another user has updated the field since you last read it. A Resync method would assign this value to the Value property. You typically use this property together with the OriginalValue property to resolve conflicts arising from batch updates.

You can assign the DataFormat property a StdDataFormat object so that you can control how values coming from the data source are formatted in the field. For more information on this property, see the section, "The DataFormat Property" in Chapter 8.

Methods

The Field object supports only two methods, both of which are used only with large binary fields such as BLOB or CLOB fields. (These are the fields whose Attributes property has the adFldLong bit set.) Because these fields can be several kilobytes—or even hundreds of kilobytes—long, writing to them and reading them back in smaller chunks is often more practical.

The AppendChunk method writes a chunk of data to a Field and expects a Variant argument that contains the data to be written. Usually, you write the contents of a file in chunks of 8 KB or 16 KB, and in most cases, you want to store a large amount of data that you have in a file, such as a long document or a bitmap. Here's a reusable routine that moves the contents of a file into a field that supports the AppendChunk method:

Sub FileToBlob(fld As ADODB.Field, FileName As String, _
    Optional ChunkSize As Long = 8192)
    Dim fnum As Integer, bytesLeft As Long, bytes As Long
    Dim tmp() As Byte
    ' Raise an error if the field doesn't support GetChunk.
    If (fld.Attributes And adFldLong) = 0 Then
        Err.Raise 1001, , "Field doesn't support the GetChunk method."
    End If
    ' Open the file; raise an error if the file doesn't exist.
    If Dir$(FileName) = " " Then Err.Raise 53, ,#"File not found"
    fnum = FreeFile
    Open FileName For Binary As fnum
    ' Read the file in chunks, and append data to the field.
    bytesLeft = LOF(fnum)
    Do While bytesLeft
        bytes = bytesLeft
        If bytes > ChunkSize Then bytes = ChunkSize
        ReDim tmp(1 To bytes) As Byte
        Get #1, , tmp
        fld.AppendChunk tmp
        bytesLeft = bytesLeft - bytes
    Loop
    Close #fnum
End Sub

The first time you call this method for a given field, it overwrites the current contents of the field; each subsequent call to this method simply appends data to the current value of the field. If you read or write another field in the record and then go back and restart appending data with the AppendChunk method, ADO assumes that you're appending a brand new value and overwrites the field's contents. ADO also overwrites the contents of the field when you start to work with another field in a Recordset clone, but not when you work with a field in another Recordset that isn't a clone of the current one.

You can use the GetChunk method to read back the data stored in a Field that contains a long binary value. This method takes one argument: the number of bytes that must be read from the Field object. The problem with this method is that if you read too many bytes, ADO will pad the returned string with spaces. Such spaces are usually something you don't want to retrieve, especially when you're working with images or other binary data. For this reason, you should test the ActualSize property to ensure that you don't read more bytes than necessary. I've prepared a reusable routine that does this testing for you automatically:

Sub BlobToFile(fld As ADODB.Field, FileName As String, _
    Optional ChunkSize As Long = 8192)
    Dim fnum As Integer, bytesLeft As Long, bytes As Long
    Dim tmp() As Byte
    ' Raise an error if the field doesn't support GetChunk.
    If (fld.Attributes And adFldLong) = 0 Then
        Err.Raise 1001, , "Field doesn't support the GetChunk method."
    End If
' Delete the file if it exists already, and then open a new one for writing.
    If Dir$(FileName) <> "" Then Kill FileName
    fnum = FreeFile
    Open FileName For Binary As fnum
    ' Read the field's contents, and write the data to the file 
    ' chunk by chunk.
    bytesLeft = fld.ActualSize
    Do While bytesLeft
        bytes = bytesLeft
        If bytes > ChunkSize Then bytes = ChunkSize
        tmp = fld.GetChunk(bytes)
        Put #fnum, , tmp
        bytesLeft = bytesLeft - bytes
    Loop
    Close #fnum
End Sub

NOTE
The FileToBlob and BlobtoFile routines are included in the library of functions in the companion CD, as are most of the other routines int hsi chapter and Chapter 14.

Multiple GetChunks methods continue to retrieve data starting from where the previous GetChunk method left off. But if you read or write the value of another field in the same Recordset (or in a clone of the Recordset), the next time you execute a GetChunk method on the original field ADO will restart from the beginning of the field. Also, remember that BLOB fields should be the last fields in SELECT queries against SQL Server data sources.

The Fields Collection

You can use the Fields collection in two distinct ways. The simplest and most intuitive way is by iterating on its items to retrieve information about the fields of a Recordset—for example, when you want to create a list of field names and values:

' Error trapping accounts for values, such as BLOB fields, that
' can't be converted to strings.
On Error Resume Next      
For i = 0 To rs.Fields.Count - 1
    lstFields.AddItem rs.Fields(i).Name & " = " & rs.Fields(i).Value
Next

The Fields collection also supports the Append method, which creates a new Field object and appends it to the collection. This method is useful when you want to manufacture a Recordset object in memory without necessarily connecting it to a data source (not immediately at least). You can use this method only with client-side Recordsets (CursorLocation = adUseClient) and only if the Recordset is closed and isn't currently associated with a Connection (ActiveConnection = Nothing). The Append method has the following syntax:

Append(Name, Type, [DefinedSize], [Attrib]) As Field

The arguments define the properties of the Field object being created. The following reusable routine creates a new stand-alone Recordset that has the same field structure of another Recordset:

Function CopyFields(rs As ADODB.Recordset) As ADODB.Recordset
    Dim newRS As New ADODB.Recordset, fld As ADODB.Field
    For Each fld In rs.Fields
        newRS.Fields.Append fld.Name, fld.Type, fld.DefinedSize, _
            fld.Attributes
    Next
    Set CopyFields = newRS
End Function

Here's another routine that creates a new stand-alone record that not only duplicates the field structure of an existing Recordset but also duplicates all the records that it contains (but without being a clone Recordset):

Function CopyRecordset(rs As ADODB.Recordset) As ADODB.Recordset
    Dim newRS As New ADODB.Recordset, fld As ADODB.Field
    Set newRS = CopyFields(rs)
    newRS.Open     ' You must open the Recordset before adding new records.
    rs.MoveFirst
    Do Until rs.EOF
        newRS.AddNew                        ' Add a new record.
        For Each fld In rs.Fields           ' Copy all fields' values.
            newRS(fld.Name) = fld.Value     ' Assumes no BLOB fields
        Next
        rs.MoveNext
    Loop 
    Set CopyRecordset = newRS
End Function

The Fields collection also supports the Delete method, which removes a field in a stand-alone record before opening it, and the Refresh method.

NOTE
Alas, it seems that you can't create hierarchical stand-alone Recordsets. In fact, if you try to create a Field whose Type property is adChapter, an error arises.